﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Windows.Forms;
using System.Threading;

namespace CommLink
{
    class TCPIPComm : CommLinkAbstract
    {
        /// <summary>
        /// Pipe data transfer length constant.
        /// </summary>
        const int nPipeDataLen = 4096;

        /// <summary>
        /// TCP client
        /// </summary>
        TcpClient nTCPClient;
        /// <summary>
        /// Read write network stream
        /// </summary>
        NetworkStream nStream;

        /// <summary>
        /// Serial communication port
        /// </summary>
        Semaphore nSCPReadBlock;

        /// <summary>
        /// Public constructor
        /// </summary>
        public TCPIPComm()
        {
        }

        /// <summary>
        /// Destructor closing the named pipe
        /// </summary>
        ~TCPIPComm()
        {
            closeTCPConnection();
        }

        /// <summary>
        /// Closes up anyform of open comm channels
        /// </summary>
        public override void closeConnection()
        {
            closeTCPConnection();
        }

        /// <summary>
        /// TCP channel shut down.
        /// </summary>
        public void closeTCPConnection()
        {
            terminating = true;

            if (nTCPClient != null && nTCPClient.Connected)
                nTCPClient.Close();

            if (OnConnectionStatusChange != null)
                OnConnectionStatusChange(ConnectionStates.NotConnected);
        }

        /// <summary>
        /// Sends a data buffer trough the named pipe to the CCS integrated dll
        /// </summary>
        /// <param name="OutData">Byte array to sned</param>
        /// <returns>Error status</returns>
        public override int messageSend(byte[] OutData)
        {
            int status = 0;
            try
            {
                if (OutData.Length > nPipeDataLen)
                {
                    MessageBox.Show("Message exceeds pipe limit of " + nPipeDataLen.ToString());
                    status = -1;
                }
                else
                {
                    nStream.Write(OutData, 0, OutData.Length);
                }
            }
            catch (Exception ex)
            {
                status = -1;
                MessageBox.Show(ex.Message);
            }

            if (status == -1 || terminating)
            {
                closeTCPConnection();
                MessageBox.Show("Server connection lost");
            }

            return status;
        }

        /// <summary>
        /// Pipe reading thread termination flag.
        /// </summary>
        Boolean terminating = false;

        /// <summary>
        /// Pipe reading thread funciton.
        /// </summary>
        void threadReadingFunction()
        {
            while (!terminating && nTCPClient.Connected)
            {
                byte[] msgHeader = comPortReadBytes(12);
                if (msgHeader == null)
                    break;

                int msgPayloadSize = 0;
                int strmPos = 8;
                for (int i = 0; i < 4; i++)
                    msgPayloadSize |= (int)(msgHeader[strmPos++] << (8 * i));

                if (msgPayloadSize <= 0 || msgPayloadSize > 4096)
                {
                    MessageBox.Show("Message payload size of " + msgPayloadSize.ToString() + " received. Discarding message.");
                    continue;
                }

                byte[] msgPayload = comPortReadBytes(msgPayloadSize);
                if (msgPayload == null)
                    break;

                byte[] msgInc = new byte[msgHeader.Length + msgPayload.Length];
                msgHeader.CopyTo(msgInc, 0);
                msgPayload.CopyTo(msgInc, msgHeader.Length);

                if (OnMessageReceived != null)
                    OnMessageReceived(msgInc);
            }
            if (!terminating)
                MessageBox.Show("Server connection lost");

            closeTCPConnection();
        }

        byte[] comPortReadBytes(int numBytesRead)
        {
            int readBufPos = 0;
            byte[] msgInc = new byte[numBytesRead];

            do
            {
                do
                {
                    if (!nTCPClient.Connected)
                        return null;

                    int toRead = numBytesRead - readBufPos;
                    if (nTCPClient.Available < toRead)
                        toRead = nTCPClient.Available;
                    try
                    {
                        nStream.Read(msgInc, readBufPos, toRead);
                    }
                    catch (Exception ex)
                    {
                        if (!terminating)
                            MessageBox.Show(ex.Message);
                        return null;
                    }
                    readBufPos += toRead;
                } while (nTCPClient.Available > 0 && readBufPos < numBytesRead);
            } while (readBufPos < numBytesRead);

            return msgInc;
        }

        /// <summary>
        /// Message received event providing the byte array data received from the pipe.
        /// </summary>
        public override event MessageReceived OnMessageReceived;

        /// <summary>
        /// Connection status change event.
        /// </summary>
        public override event ConnectionStatusChange OnConnectionStatusChange;

        /// <summary>
        /// Opens communication channels
        /// </summary>
        public override void openConnection()
        {
            bool connectFailed = false;

            try
            {
                if (Properties.Settings.Default.TCP_HostAddr == "NO_HOST_DEFINED")
                {
                    nTCPClient = new TcpClient();
                    Properties.Settings.Default.TCP_HostAddr = "192.168.0.5";
                    Properties.Settings.Default.TCP_HostPort = 1234;
                    Properties.Settings.Default.Save();
                }
                else
                {
                    string hostAddr = Properties.Settings.Default.TCP_HostAddr;
                    int hostPort = Properties.Settings.Default.TCP_HostPort;

                    nTCPClient = new TcpClient(hostAddr, hostPort);
                }

                nTCPClient.ReceiveBufferSize = nPipeDataLen;
                nTCPClient.SendBufferSize = nPipeDataLen;
                nStream = nTCPClient.GetStream();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to connect to remote host.\n" + ex.Message);
                connectFailed = true;
            }

            if (!connectFailed)
            {
                try
                {
                    nSCPReadBlock = new Semaphore(0, 100);

                    Thread threadReading = new Thread(new ThreadStart(threadReadingFunction));
                    threadReading.IsBackground = true;
                    threadReading.Start();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Failed to start channel service.\n" + ex.Message);
                    connectFailed = true;
                }
            }

            if (OnConnectionStatusChange != null)
            {
                if (connectFailed)
                    OnConnectionStatusChange(ConnectionStates.ServerError);
                else
                    OnConnectionStatusChange(ConnectionStates.Connected);
            }
        }

        /// <summary>
        /// Returns connection type name
        /// </summary>
        /// <returns>Connection type name</returns>
        public override string getName()
        {
            return "TCP/IP channel";
        }
    }
}
